package cmich.cps.svn.utility.gui;

import java.util.List;
import java.util.logging.Logger;

import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.WindowConstants;
import javax.swing.border.EmptyBorder;
import javax.swing.JComboBox;

import cmich.cps.svn.utility.Assignment;
import cmich.cps.svn.utility.Grader;
import cmich.cps.svn.utility.Repository;
import cmich.cps.svn.utility.UtilitySystem;

import javax.swing.JLabel;
import javax.swing.JCheckBox;
import javax.swing.JTextField;
import javax.swing.border.TitledBorder;

import java.awt.Cursor;
import java.awt.GridBagLayout;
import java.awt.GridBagConstraints;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

/**
 * The Class GUIFrame.
 *
 * @author Jonathan Kissinger "<kissi1j@cmich.edu>"
 */
public class GUIFrame extends JFrame {
	
	/** The Constant serialVersionUID. */
	private static final long serialVersionUID = 1L;
	
	/** The Constant main. */
	private static final GUIFrame frame = new GUIFrame();
	
	/** The content pane. */
	private JPanel contentPane;
	
	/** The Constant LOGGER. */
	private final static Logger LOGGER = Logger.getLogger(GUIFrame.class.getName());
	
	/** The lbl active repository. */
	private JLabel lblActiveRepository;
	
	/** The lbl active assignment. */
	private JLabel lblActiveAssignment;
	
	/** The assign combo box. */
	private JComboBox<Assignment> assignComboBox;
	
	/** The repo combo box. */
	private JComboBox<Repository> repoComboBox;
	
	/** The assign properties panel. */
	private JPanel assignPropertiesPanel;
	
	/** The chckbx require commit. */
	private JCheckBox chckbxRequireCommit;
	
	/** The chckbx allow late commits. */
	private JCheckBox chckbxAllowLateCommits;
	
	/** The txt due date. */
	private JTextField txtDueDate;
	
	/** The lbl due date. */
	private JLabel lblDueDate;
	
	/** The btn new repository. */
	private JButton btnNewRepository;
	
	/** The btn new assignment. */
	private JButton btnNewAssignment;
	
	/** The btn grade assignment. */
	private JButton btnGradeAssignment;
	
	/** The btn save changes. */
	private JButton btnSaveChanges;
	
	/** The btn add file to. */
	private JButton btnAddFileTo;
	
	/** The btn add test to. */
	private JButton btnAddTestTo;
	
	/** The system. */
	private UtilitySystem system;
	
	/** The is updating. */
	private boolean isUpdating;
	
	/** The btn remove file from. */
	private JButton btnRemoveFileFrom;
	
	/** The progress bar. */
	private JProgressBar progressBar;

	/**
	 * Instantiates a new gUI frame.
	 */
	private GUIFrame() {
		createWindow();
	}

	/**
	 * Gets the single instance of GUIFrame.
	 *
	 * @return single instance of GUIFrame
	 */
	public static GUIFrame getInstance() {
		return frame;
	}

	/**
	 * Creates the window.
	 */
	public void createWindow() {
		system = UtilitySystem.getInstance();
		try {
			try {
				UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			} catch (UnsupportedLookAndFeelException e) {
				e.printStackTrace();
			}
		} catch (ClassNotFoundException e) {
			LOGGER.warning(e.toString());
		}
		setTitle("CMU SVN Utility");
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setBounds(0, 0, 800, 300);
		contentPane = new JPanel();
		contentPane.setBorder(new EmptyBorder(5, 5, 5, 0));
		setContentPane(contentPane);
		setVisible(true);
		toFront();
		setExtendedState(NORMAL);
		setLocationRelativeTo(null);

		Image image = Toolkit.getDefaultToolkit().getImage(getClass().getClassLoader().getResource("logo.png"));
		setIconImage(image);

		addButtons();
		addPropertiesPanel();
	}

	/**
	 * Adds the buttons.
	 */
	public void addButtons() {
		GridBagLayout gbl_contentPane = new GridBagLayout();
		gbl_contentPane.columnWidths = new int[] {100, 200, 200, 100};
		gbl_contentPane.rowHeights = new int[] {25, 25, 25, 25, 25};
		gbl_contentPane.columnWeights = new double[]{0.0, 0.0, 0.1, 0.0};
		gbl_contentPane.rowWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0};
		contentPane.setLayout(gbl_contentPane);

		lblActiveRepository = new JLabel("Active Repository:");
		GridBagConstraints gbc_lblActiveRepository = new GridBagConstraints();
		gbc_lblActiveRepository.anchor = GridBagConstraints.WEST;
		gbc_lblActiveRepository.insets = new Insets(0, 0, 5, 5);
		gbc_lblActiveRepository.gridx = 0;
		gbc_lblActiveRepository.gridy = 0;
		contentPane.add(lblActiveRepository, gbc_lblActiveRepository);

		repoComboBox = new JComboBox<Repository>();
		GridBagConstraints gbc_repoComboBox = new GridBagConstraints();
		gbc_repoComboBox.gridwidth = 2;
		gbc_repoComboBox.fill = GridBagConstraints.HORIZONTAL;
		gbc_repoComboBox.insets = new Insets(0, 0, 5, 5);
		gbc_repoComboBox.gridx = 1;
		gbc_repoComboBox.gridy = 0;
		contentPane.add(repoComboBox, gbc_repoComboBox);

		btnNewRepository = new JButton("New Repository");
		GridBagConstraints gbc_btnNewRepository = new GridBagConstraints();
		gbc_btnNewRepository.anchor = GridBagConstraints.NORTHEAST;
		gbc_btnNewRepository.insets = new Insets(0, 0, 5, 0);
		gbc_btnNewRepository.gridx = 3;
		gbc_btnNewRepository.gridy = 0;
		contentPane.add(btnNewRepository, gbc_btnNewRepository);

		lblActiveAssignment = new JLabel("Active Assignment:");
		GridBagConstraints gbc_lblActiveAssignment = new GridBagConstraints();
		gbc_lblActiveAssignment.anchor = GridBagConstraints.WEST;
		gbc_lblActiveAssignment.insets = new Insets(0, 0, 5, 5);
		gbc_lblActiveAssignment.gridx = 0;
		gbc_lblActiveAssignment.gridy = 1;
		contentPane.add(lblActiveAssignment, gbc_lblActiveAssignment);

		assignComboBox = new JComboBox<Assignment>();
		GridBagConstraints gbc_assignComboBox = new GridBagConstraints();
		gbc_assignComboBox.gridwidth = 2;
		gbc_assignComboBox.fill = GridBagConstraints.HORIZONTAL;
		gbc_assignComboBox.insets = new Insets(0, 0, 5, 5);
		gbc_assignComboBox.gridx = 1;
		gbc_assignComboBox.gridy = 1;
		contentPane.add(assignComboBox, gbc_assignComboBox);

		btnNewAssignment = new JButton("New Assignment");
		GridBagConstraints gbc_btnNewAssignment = new GridBagConstraints();
		gbc_btnNewAssignment.anchor = GridBagConstraints.NORTHEAST;
		gbc_btnNewAssignment.insets = new Insets(0, 0, 5, 0);
		gbc_btnNewAssignment.gridx = 3;
		gbc_btnNewAssignment.gridy = 1;
		contentPane.add(btnNewAssignment, gbc_btnNewAssignment);

		btnAddFileTo = new JButton("Add File to Student Directories");
		GridBagConstraints gbc_btnAddFileTo = new GridBagConstraints();
		gbc_btnAddFileTo.anchor = GridBagConstraints.WEST;
		gbc_btnAddFileTo.insets = new Insets(0, 0, 5, 5);
		gbc_btnAddFileTo.gridx = 2;
		gbc_btnAddFileTo.gridy = 2;
		contentPane.add(btnAddFileTo, gbc_btnAddFileTo);

		btnRemoveFileFrom = new JButton("Remove File/Directory from Student Directories");
		GridBagConstraints gbc_btnRemoveFileFrom = new GridBagConstraints();
		gbc_btnRemoveFileFrom.anchor = GridBagConstraints.WEST;
		gbc_btnRemoveFileFrom.insets = new Insets(0, 0, 5, 5);
		gbc_btnRemoveFileFrom.gridx = 2;
		gbc_btnRemoveFileFrom.gridy = 3;
		contentPane.add(btnRemoveFileFrom, gbc_btnRemoveFileFrom);

		btnAddTestTo = new JButton("Add Unit Test to Assignment");
		GridBagConstraints gbc_btnAddTestTo = new GridBagConstraints();
		gbc_btnAddTestTo.anchor = GridBagConstraints.WEST;
		gbc_btnAddTestTo.insets = new Insets(0, 0, 5, 5);
		gbc_btnAddTestTo.gridx = 2;
		gbc_btnAddTestTo.gridy = 4;
		contentPane.add(btnAddTestTo, gbc_btnAddTestTo);

		btnGradeAssignment = new JButton("Grade Assignment");
		GridBagConstraints gbc_btnGradeAssignment = new GridBagConstraints();
		gbc_btnGradeAssignment.anchor = GridBagConstraints.WEST;
		gbc_btnGradeAssignment.insets = new Insets(0, 0, 5, 5);
		gbc_btnGradeAssignment.gridx = 2;
		gbc_btnGradeAssignment.gridy = 5;
		contentPane.add(btnGradeAssignment, gbc_btnGradeAssignment);
	}

	/**
	 * Adds the properties panel.
	 */
	public void addPropertiesPanel() {
		assignPropertiesPanel = new JPanel();
		assignPropertiesPanel.setBorder(new TitledBorder(null, "Assignment Properties", TitledBorder.LEADING, TitledBorder.TOP, null, null));
		GridBagConstraints gbc_assignPropertiesPanel = new GridBagConstraints();
		gbc_assignPropertiesPanel.gridwidth = 2;
		gbc_assignPropertiesPanel.gridheight = 5;
		gbc_assignPropertiesPanel.fill = GridBagConstraints.BOTH;
		gbc_assignPropertiesPanel.insets = new Insets(0, 0, 5, 5);
		gbc_assignPropertiesPanel.gridx = 0;
		gbc_assignPropertiesPanel.gridy = 2;
		contentPane.add(assignPropertiesPanel, gbc_assignPropertiesPanel);
		GridBagLayout gbl_assignPropertiesPanel = new GridBagLayout();
		gbl_assignPropertiesPanel.columnWidths = new int[]{48, 94};
		gbl_assignPropertiesPanel.rowHeights = new int[]{19, 23, 23, 20, 34, 23, 0};
		gbl_assignPropertiesPanel.columnWeights = new double[]{0.0, 0.1};
		gbl_assignPropertiesPanel.rowWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
		assignPropertiesPanel.setLayout(gbl_assignPropertiesPanel);

		chckbxRequireCommit = new JCheckBox("Require commit logs");
		chckbxRequireCommit.setToolTipText("Require students to enter text in the commit log every time they make a new commit to the repository.");
		GridBagConstraints gbc_chckbxRequireCommit = new GridBagConstraints();
		gbc_chckbxRequireCommit.anchor = GridBagConstraints.NORTHWEST;
		gbc_chckbxRequireCommit.insets = new Insets(0, 0, 5, 0);
		gbc_chckbxRequireCommit.gridwidth = 2;
		gbc_chckbxRequireCommit.gridx = 0;
		gbc_chckbxRequireCommit.gridy = 0;
		assignPropertiesPanel.add(chckbxRequireCommit, gbc_chckbxRequireCommit);

		chckbxAllowLateCommits = new JCheckBox("Allow late commits");
		chckbxAllowLateCommits.setToolTipText("<html>If enabled, assignments will still be marked as late even though they are accepted.<br>If disabled, once the due date is past the repository will no longer accept commits to the assignment's directory.</html>");
		GridBagConstraints gbc_chckbxAllowLateCommits = new GridBagConstraints();
		gbc_chckbxAllowLateCommits.anchor = GridBagConstraints.NORTHWEST;
		gbc_chckbxAllowLateCommits.insets = new Insets(0, 0, 5, 0);
		gbc_chckbxAllowLateCommits.gridwidth = 2;
		gbc_chckbxAllowLateCommits.gridx = 0;
		gbc_chckbxAllowLateCommits.gridy = 1;
		assignPropertiesPanel.add(chckbxAllowLateCommits, gbc_chckbxAllowLateCommits);

		lblDueDate = new JLabel("Due date:");
		GridBagConstraints gbc_lblDueDate = new GridBagConstraints();
		gbc_lblDueDate.anchor = GridBagConstraints.EAST;
		gbc_lblDueDate.insets = new Insets(0, 0, 5, 5);
		gbc_lblDueDate.gridx = 0;
		gbc_lblDueDate.gridy = 2;
		assignPropertiesPanel.add(lblDueDate, gbc_lblDueDate);

		txtDueDate = new JTextField();
		GridBagConstraints gbc_txtDueDate = new GridBagConstraints();
		gbc_txtDueDate.fill = GridBagConstraints.HORIZONTAL;
		gbc_txtDueDate.anchor = GridBagConstraints.NORTH;
		gbc_txtDueDate.insets = new Insets(0, 0, 5, 0);
		gbc_txtDueDate.gridx = 1;
		gbc_txtDueDate.gridy = 2;
		assignPropertiesPanel.add(txtDueDate, gbc_txtDueDate);
		txtDueDate.setColumns(10);

		btnSaveChanges = new JButton("Save Changes");
		GridBagConstraints gbc_btnSaveChanges = new GridBagConstraints();
		gbc_btnSaveChanges.anchor = GridBagConstraints.SOUTH;
		gbc_btnSaveChanges.gridwidth = 2;
		gbc_btnSaveChanges.gridx = 0;
		gbc_btnSaveChanges.gridy = 4;
		assignPropertiesPanel.add(btnSaveChanges, gbc_btnSaveChanges);
	}

	/**
	 * Update buttons.
	 */
	public void updateButtons() {
		LOGGER.finer("Updating gui buttons.");
		this.isUpdating = true;
		repoComboBox.removeAllItems();
		int selection = -1;
		List<Repository> repos = system.getRepositories();
		Repository repo = system.getActiveRepo();
		for(int i=0;i<repos.size();i++) {
			repoComboBox.addItem(repos.get(i));
			if(repos.get(i).equals(repo)) {
				selection = i;
			}
		}
		repoComboBox.setSelectedIndex(selection);

		assignComboBox.removeAllItems();
		selection = -1;
		List<Assignment> assignments = repo.getAssignments();
		Assignment active = repo.getActiveAssignment();
		for(int i=0;i<assignments.size();i++) {
			assignComboBox.addItem(assignments.get(i));
			if(assignments.get(i).equals(active)) {
				selection = i;
			}
		}
		assignComboBox.setSelectedIndex(selection);
		this.isUpdating = false;

		updateProperties();
	}

	/**
	 * Update properties.
	 */
	public void updateProperties() {
		Assignment active = system.getActiveRepo().getActiveAssignment();
		LOGGER.finer("Updating gui properties panel for assignment " + active);
		chckbxRequireCommit.setSelected(active.getRequireCommitLog());
		chckbxAllowLateCommits.setSelected(active.getAllowLateCommits());
		txtDueDate.setText(active.getDueDateString());
	}

	/**
	 * Adds the functionality.
	 */
	public void addFunctionality() {
		repoComboBox.addItemListener(new comboBoxItemListener());
		assignComboBox.addItemListener(new comboBoxItemListener());
		btnNewRepository.addActionListener(new ActionListenerWithDelayCursor() {
			@Override
			public void performActions(ActionEvent e) {
				system.addNewRepo();
				updateButtons();
			}
		});
		btnNewAssignment.addActionListener(new ActionListenerWithDelayCursor() {
			@Override
			public void performActions(ActionEvent e) {
				system.getActiveRepo().addNewAssignment();
				updateButtons();
			}
		});
		btnSaveChanges.addActionListener(new ActionListenerWithDelayCursor() {
			@Override
			public void performActions(ActionEvent e) {
				system.getActiveRepo().setProperties(chckbxAllowLateCommits.isSelected(), chckbxRequireCommit.isSelected(), txtDueDate.getText());
			}
		});
		btnAddFileTo.addActionListener(new ActionListenerWithDelayCursor() {
			@Override
			public void performActions(ActionEvent e) {
				system.getActiveRepo().addFileToActiveAssignment();
			}
		});
		btnRemoveFileFrom.addActionListener(new ActionListenerWithDelayCursor() {
			@Override
			public void performActions(ActionEvent e) {
				system.getActiveRepo().removeFileFromActiveAssignment();
			}
		});
		btnAddTestTo.addActionListener(new ActionListenerWithDelayCursor() {
			@Override
			public void performActions(ActionEvent e) {
				system.getActiveRepo().addUnitTest();
			}
		});
		btnGradeAssignment.addActionListener(new ActionListenerWithDelayCursor() {
			@Override
			public void performActions(ActionEvent e) {
				final ProgressBarDialog bar = new ProgressBarDialog(frame);
				Grader grader = new Grader();
				
				grader.addPropertyChangeListener(new PropertyChangeListener() {
					@Override
					public void propertyChange(PropertyChangeEvent evt) {
						if ("progress"==(evt.getPropertyName())) {
							bar.setVisible(true);
							int progress = (Integer) evt.getNewValue();
							progressBar.setValue(progress);
							progressBar.setString(progress + "%");
							if(progress==100) {
								bar.setVisible(false);
							}
						}
					}
				});
				grader.execute();
			}
		});
	}

	/**
	 * Sets the active.
	 *
	 * @param item the new active
	 */
	public void setActive(Object item) {
		if(item instanceof Assignment) {
			Assignment assignment = (Assignment) item;
			system.getActiveRepo().setActiveAssignment(assignment);
			updateProperties();
		}
		else if (item instanceof Repository) {
			Repository repo = (Repository) item;
			system.setActiveRepo(repo);
			updateButtons();
		}
	}

	/**
	 * The listener interface for receiving comboBoxItem events.
	 * The class that is interested in processing a comboBoxItem
	 * event implements this interface, and the object created
	 * with that class is registered with a component using the
	 * component's <code>addcomboBoxItemListener<code> method. When
	 * the comboBoxItem event occurs, that object's appropriate
	 * method is invoked.
	 *
	 * @see comboBoxItemEvent
	 */
	private class comboBoxItemListener implements ItemListener {
		
		/* (non-Javadoc)
		 * @see java.awt.event.ItemListener#itemStateChanged(java.awt.event.ItemEvent)
		 */
		@Override
		public void itemStateChanged(ItemEvent e) {
			if ((e.getStateChange() == ItemEvent.SELECTED) && !isUpdating) {
				setActive(e.getItem());
			}
		}
	}

	/**
	 * Some of the methods require lots of time to complete, so I added
	 * a wait cursor display method to the class.
	 * 
	 * @author Jonathan Kissinger <kissi1j@cmich.edu>
	 */
	private class ActionListenerWithDelayCursor implements ActionListener {
		
		/* (non-Javadoc)
		 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
		 */
		@Override
		public void actionPerformed(final ActionEvent e) {
			contentPane.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
			performActions(e);
			contentPane.setCursor(Cursor.getDefaultCursor());				
		}

		/**
		 * Performs the actions that may take a while
		 *
		 * @param e the action event triggering this listener
		 */
		public void performActions(ActionEvent e) {}		
	}

	/**
	 * The Class ProgressBarDialog.
	 * Used solely by Grader, it displays a progress bar because running JUnit
	 * tests on 20+ students may take a very long time.
	 *
	 * @author Jonathan Kissinger <kissi1j@cmich.edu>
	 */
	private class ProgressBarDialog extends JDialog {
		
		/** The Constant serialVersionUID. */
		private static final long serialVersionUID = 1L;
		
		/**
		 * Instantiates a new progress bar dialog.
		 *
		 * @param parent the parent {@link JFrame}
		 */
		public ProgressBarDialog(JFrame parent) {
			super(parent, true);
			progressBar = new JProgressBar(0, 100);
			progressBar.setValue(0);
			progressBar.setStringPainted(true);
			this.add(progressBar);
			pack();
			setResizable(false);
			setLocationRelativeTo(parent);
			setVisible(false);
			setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
			setTitle("Grading progress");
		}
	}  
}

